package com.kerwin.aspects;

import com.kerwin.annotations.Logs;
import com.kerwin.exception.HandlerException;
import com.kerwin.logmgt.SysOperateLogsService;
import com.kerwin.sysmgt.entity.SysOperateLogs;
import com.kerwin.utils.DateUtil;
import com.kerwin.utils.SpringContextUtil;
import com.kerwin.utils.UserThreadLocal;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/*进到容器*/
@Component
///*表示这是个切面对象*/ /*按照spring的性格，居然没有提供符合注解*/
@Aspect
public class LogAspect {
    /**
    目标对象target：核心业务对象，原对象，被代理对象。AliPayServiceImpl
    连接点方法joinpoint：需要被扩展的方法，pay
    切入点pointcut：通过一个表达式，来描述出来一系列的连接点的集合。
    织入weave：通过动态代理模式创建代理对象，并把切面中的通知方法  插入  到目标对象的连接点方法的过程。
    切面aspect：辅助业务对象
    通知advice：切面中的一个方法，前置通知，返回通知，异常通知，最终通知
     */

    /*要写一个切入点表达式*/
    /*用来定义位置 那个类下的哪一些类下的哪些方法*/
    /*注意是多个被标注注解的方法，最后都进到一个 aop切面统一进行通知*/
    /*@annotation 谁是我的连接点方法呢，所有带有Logs注解的方法，都是我的连接点方法*/
    @Pointcut("@annotation(com.kerwin.annotations.Logs)")
    public void logPointcut(){}
    /*要有方法来承载注解*/

    @Around("logPointcut()")


    /*我需要调用service把日志参数存进数据库*/
    /*我试试这算不算循环调用*/
//    @Autowired
//    UserService userService;




    /*返回值必须是Object*/
    /*形参必须是这个 这里面封装这我要的所有参数*/
    public Object doLog(ProceedingJoinPoint jp) {
        Object result = null;
        /*这个是方法是实参*/
        String paramStr=null;

        /*因为要做操作日志  需要有一个日志对象，记录到数据库里面*/
        SysOperateLogs sysLogs = new SysOperateLogs();


        try {
            /*前置通知*/
            Object[] args = jp.getArgs();
            StringBuilder stringBuilder=new StringBuilder();
            /*什么post请求也能获取到参数！！！*/
            for (int i = 0; i < args.length; i++) {

                /*这是最后一个参数*/
                if(!(i==args.length-1)){
                    stringBuilder.append(args[i].toString()+",");
                }else {
                    stringBuilder.append(args[i].toString());
                }
                paramStr = stringBuilder.toString();
            }
            /*方法的参数存进对象里*/
            sysLogs.setOperatingParam(paramStr);
            /*模块名*/
            /*拿到注解*/
            Signature signature = jp.getSignature();
            /*Signature是个接口*/
            /*MethodSignature是他的实现类*/
            MethodSignature ms=(MethodSignature)signature;
            Method method = ms.getMethod();
            /*这里拿到注解的内容了*/
            Logs annotation = method.getAnnotation(Logs.class);
            String module = annotation.module();
            sysLogs.setModuleName(module);
            /*操作方法名*/
            String operate = annotation.operate();
            sysLogs.setOperateName(operate);
            /*操作时间*/
            sysLogs.setOperateTime(DateUtil.nowTime());

            /*操作结果 写在下面了*/



            /*这个是反射执行，执行真正的方法*/
            /*相当于method.invoke(target,args)*/
//因为在 try里面 需要把Object proceed 提出来
//Object proceed = jp.proceed();
            result = jp.proceed();
            /*后置通知*/


            /*犯了个重大错误，这个操作人是上面登录方法，走完才会放到线程里面的*/
            /*我在方法之前调用肯定会出错啊*/

            /*操作人*/
            String userName = UserThreadLocal.getUserName();
            sysLogs.setUser(userName);

            /*操作结果*/
            sysLogs.setOperateResult(true);
        } catch (Throwable e) {
            /*异常通知*/
            /*设置错误信息*/
            sysLogs.setErrorMessage(e.getMessage());

            if(e instanceof HandlerException){
                String msg = ((HandlerException) e).getE().getMsg();
                sysLogs.setErrorMessage(msg);
            }

            /*设置执行结果*/
            sysLogs.setOperateResult(false);

            /*要抛出去异常，全局异常才能生效*/
            /*assert意为断言的意思，这个关键字可以判断布尔值的结果是否和预期的一样，
            如果一样就正常执行，否则会抛出AssertionError。*/

            assert e instanceof RuntimeException;
            throw (RuntimeException)e;
        }
        /*需要自己补充最终通知*/
        finally {
            /*最终通知*/
            /*最终要把操作日志记录到库里面*/
            /*我靠我要在这里调用到 service里面的方法 那样就会循环调用了，我试试注入service对象*/
            /*从容器里面取试试*/
           SysOperateLogsService bean = SpringContextUtil.getBean(SysOperateLogsService.class);
            Integer integer = bean.addUserOperaLog(sysLogs);
            System.out.println(sysLogs.toString());
        }

        /*这个返回值就是我的方法的返回值*/
        /*比如我的控制层 返回了这个AxiosResultGenerics json对象*/
        return result;
    }
}
